﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using System.Timers;
using System.Xml.Linq;
using CommunicatorAPI;
using mshtml;
using System.Linq;
using System.Diagnostics;
using System.Text.RegularExpressions;
using Microsoft.Lync.Model;

namespace CommunicatorHistories.Managers
{
    public delegate void ContactListChangedEventHandler(object sender, ContactListEventArgs e);

    public class ContactListEventArgs : EventArgs
    {
        public List<string> Contacts { get; set; }
    }

    public class CommunicatorMessageWrapper
    {
        #region [Import windows API functions]

        [DllImport("user32.dll", EntryPoint = "GetClassNameA")]
        public static extern int GetClassName(IntPtr hwnd, StringBuilder lpClassName, int nMaxCount);
        public delegate int EnumProc(IntPtr hWnd, ref IntPtr lParam);

        [DllImport("user32.dll")]
        public static extern int EnumChildWindows(IntPtr hWndParent, EnumProc lpEnumFunc, ref  IntPtr lParam);
        [DllImport("user32.dll", EntryPoint = "RegisterWindowMessageA")]
        public static extern int RegisterWindowMessage(string lpString);
        [DllImport("user32.dll", EntryPoint = "SendMessageTimeoutA")]
        public static extern int SendMessageTimeout(IntPtr hwnd, int msg, int wParam, int lParam, int fuFlags, int uTimeout, out int lpdwResult);
        [DllImport("OLEACC.dll")]
        public static extern int ObjectFromLresult(int lResult, ref Guid riid, int wParam, ref IHTMLDocument2 ppvObject);
        public const int SMTO_ABORTIFHUNG = 0x2;
        public Guid IID_IHTMLDocument = new Guid("626FC520-A41E-11CF-A731-00A0C9082637");

        #endregion

        #region [Private fields]

        private IHTMLDocument2 document;
        private Timer SaveHistoryTimer;
        private List<Converstation> Converstations;
        private Messenger communicator;
        private FileSystemWatcher HistoryWatcher;
        private List<LyncConversationWraper> LyncConversations;

        #endregion

        #region [Public properties]

        public string CommunicatorHistoryFolder
        {
            get
            {
                string folder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "CommunicatorHistory");
                if (!Directory.Exists(folder)) Directory.CreateDirectory(folder);
                return folder;
            }
        }

        public string MyContactName
        {
            get
            {
                return communicator.MyFriendlyName;
            }
        }

        #endregion

        public event ContactListChangedEventHandler OnContactListChanged;

        public CommunicatorMessageWrapper()
        {
            Converstations = new List<Converstation>();
            this.LyncConversations = new List<LyncConversationWraper>();
            HistoryWatcher = new FileSystemWatcher(this.CommunicatorHistoryFolder, this.CreateContactHistoryFolder("*"));
            HistoryWatcher.EnableRaisingEvents = true;
            HistoryWatcher.Created += new FileSystemEventHandler(ContactListChanged);
            HistoryWatcher.Deleted += new FileSystemEventHandler(ContactListChanged);
        }

        #region [ Converstation reader ]

        public List<string> Contacts
        {
            get
            {
                var allcontacts = Directory.GetDirectories(this.CommunicatorHistoryFolder, this.CreateContactHistoryFolder("*"), SearchOption.TopDirectoryOnly);
                return allcontacts
                   .Select(f => Path.GetFileName(f).Replace(this.CreateContactHistoryFolder(""), ""))
                   .ToList();
            }
        }

        public string GetHistoryForContact(string contactName)
        {
            return GetHistoryForContact(contactName, null);
        }

        public string GetHistoryForContact(string contactName, DateTime? from)
        {
            List<ContactHistory> res = new List<ContactHistory>();
            string folder = Path.Combine(this.CommunicatorHistoryFolder, this.CreateContactHistoryFolder(contactName));
            string history = string.Empty;

            foreach (var converctationFile in Directory.GetFiles(folder, "*.txt", SearchOption.TopDirectoryOnly))
            {
                try
                {
                    var strdate =  Path.GetFileNameWithoutExtension(converctationFile).Split(new string[]{"-"}, StringSplitOptions.RemoveEmptyEntries);
                    int MM = int.Parse(strdate[0]);
                    int dd =int.Parse(strdate[1]);
                    int yyyy = int.Parse(strdate[2]);
                    int HH = int.Parse(strdate[3]);
                    int mm = int.Parse(strdate[4]);
                    int ss = int.Parse(strdate[5]);

                    var date = new DateTime(yyyy, MM, dd, HH, mm, ss);
                  
                    if ((from.HasValue && date >= from.Value) || (from == null)) 
                    {
                        using (StreamReader sr = new StreamReader(converctationFile))
                        {
                            res.Add(
                                new ContactHistory
                                {
                                    Date = date,
                                    Text = ReplaceHistory(sr.ReadToEnd())
                                });
                        }
                    }
                }
                catch { }
            }
            return "<html><head><meta HTTP-EQUIV=\"Content-Type\" content=\"text/html; charset=utf-8\" /> <script type=\"text/javascript\">function pointToBottom(){  window.location = \"#bottomlink\";}</script></head> <body onLoad=\"pointToBottom()\">" + string.Join("", res.OrderBy(h => h.Date).Select(h => h.Text).ToArray()) + "<a name=\"bottomlink\">&nbsp;</a></body></html>";
        }

        string ReplaceBetween(string s, string begin, string end, string replace)
        {
            Regex regex = new Regex(string.Format("\\{0}.*?\\{1}", begin, end));
            return regex.Replace(s, replace);
        }


        private string ReplaceHistory(string history)
        {
            history = ReplaceBetween(history, "<OBJECT", "</OBJECT>", "&#149;");
            history = history.Replace("<A ", "<A target=\"blank\" ");
            return history;
        }

        void ContactListChanged(object sender, FileSystemEventArgs e)
        {
            if (this.OnContactListChanged != null)
            {
                this.OnContactListChanged(sender, new ContactListEventArgs { Contacts = this.Contacts });
            }
        }

        #endregion

        private bool IsProcessOpen(string name)
        {
            return Process.GetProcesses().Where(p => p.ProcessName.Contains(name)).Count() > 0;
            
        }

        private Timer CommunicatorWatcher;
        
        private void RurnOnHistrory()
        {
            try
            {
                SaveHistoryTimer = new Timer(1000);
                SaveHistoryTimer.Elapsed += new ElapsedEventHandler(SaveTimer_Elapsed);

                communicator = new Messenger();
                communicator.OnIMWindowContactAdded += new DMessengerEvents_OnIMWindowContactAddedEventHandler(communicator_OnIMWindowContactAdded);
                communicator.OnIMWindowContactRemoved += new DMessengerEvents_OnIMWindowContactRemovedEventHandler(communicator_OnIMWindowContactRemoved);
            }
            catch (Exception e)
            {

            }
        }

        protected  virtual void RegisterLyncWatcher()
        {
            var lyncClient = LyncClient.GetClient();
            lyncClient.ConversationManager.ConversationAdded += ConversationManager_ConversationAdded;
            lyncClient.ConversationManager.ConversationRemoved += ConversationManager_ConversationRemoved;                        
        }

        void ConversationManager_ConversationRemoved(object sender, Microsoft.Lync.Model.Conversation.ConversationManagerEventArgs e)
        {
            var w=LyncConversations.FirstOrDefault(c => c.Conversation == e.Conversation);
            if (w == null) return;
            w.Dispose();            
        }

        void ConversationManager_ConversationAdded(object sender, Microsoft.Lync.Model.Conversation.ConversationManagerEventArgs e)
        {
            LyncConversations.Add(new LyncConversationWraper(e.Conversation, (c, m) => { SaveHistory(c, m); return true; }));
        }

        
        public void RunCommunicatorHistoryWather()
        {
            RegisterLyncWatcher();
            if (this.IsProcessOpen("communicator"))
                this.RurnOnHistrory();
            else
            {
                CommunicatorWatcher = new Timer(10000);
                CommunicatorWatcher.Elapsed += new ElapsedEventHandler(CommunicatorWatcher_Elapsed);
                CommunicatorWatcher.Start();
            }
        }

        void CommunicatorWatcher_Elapsed(object sender, ElapsedEventArgs e)
        {
            RurnOnHistrory();
            CommunicatorWatcher.Stop();
        }


        #region [ Save converstation data]

        private string CreateContactHistoryFolder(string contactName)
        {
            return string.Format("Contact - {0}", contactName);
        }

        private string CreateContactHistoryFile(string contactName)
        {
            string folder = Path.Combine(this.CommunicatorHistoryFolder, this.CreateContactHistoryFolder(contactName));
            if (!Directory.Exists(folder)) Directory.CreateDirectory(folder);
            return Path.Combine(folder, string.Format("{0}.txt", DateTime.Now.ToString("MM-dd-yyyy-HH-mm-ss")));
        }

        object sync = new object();
        private void SaveHistory(string contactName, string history)
        {
            lock (sync)
            {
                var contactHistoryFile = this.CreateContactHistoryFile(contactName);
                using (var sw = new StreamWriter(contactHistoryFile))
                {
                    sw.Write(history);
                }
            }
        }

        #endregion

        #region [Receive converstation text from communicator]

        private void communicator_OnIMWindowContactRemoved(object pContact, object pIMWindow)
        {
            IMessengerContact contact = pContact as IMessengerContact;
            this.RemoveConverstation((pContact as IMessengerContact).FriendlyName);
        }

        private void communicator_OnIMWindowContactAdded(object pContact, object pIMWindow)
        {
            CommunicatorAPI.IMessengerConversationWndAdvanced imWindow = pIMWindow as CommunicatorAPI.IMessengerConversationWndAdvanced;
            this.RegisterConverstation((pContact as IMessengerContact).FriendlyName, imWindow.HWND);
        }

        private void RegisterConverstation(string contactName, int hwnd)
        {
            Converstations.Add(new Converstation
            {
                ContactName = contactName,
                HWND = hwnd
            });
            this.SaveHistoryTimer.Start();
        }

        private void RemoveConverstation(string contactName)
        {
            Converstation current = this.FoundConverstation(contactName);
            if (current != null)
            {
                this.SaveHistory(current.ContactName, current.Text);
                Converstations.Remove(current);
            }
            Converstations.RemoveAll(c => c.ContactName == contactName);
            if (this.Converstations.Count == 0)
            {
                this.SaveHistoryTimer.Stop();
            }
        }

        private Converstation FoundConverstation(string contactName)
        {
            foreach (var converstation in this.Converstations)
            {
                if (converstation.ContactName == contactName)
                {
                    return converstation;
                }
            }
            return null;
        }

        private void SaveTimer_Elapsed(object sender, ElapsedEventArgs e)
        {
            foreach (var converstation in Converstations)
            {
                this.Read(converstation);
            }
        }

        private IntPtr HistoryWindow(IntPtr hWndParrent)
        {
            EnumProc proc = new EnumProc(EnumWindows);
            EnumChildWindows(hWndParrent, proc, ref hWndParrent);
            return hWndParrent;
        }

        private bool Read(Converstation converstation)
        {
            bool result = false;

            if (converstation.ConverstationHwnd == IntPtr.Zero)
            {
                converstation.ConverstationHwnd = HistoryWindow(new IntPtr(converstation.HWND));
            }

            IntPtr hWnd = converstation.ConverstationHwnd;
            if (!hWnd.Equals(IntPtr.Zero))
            {
                int lngMsg = RegisterWindowMessage("WM_HTML_GETOBJECT");
                if (lngMsg != 0)
                {
                    int lRes;
                    SendMessageTimeout(hWnd, lngMsg, 0, 0, SMTO_ABORTIFHUNG, 1000, out lRes);
                    if (!(bool)(lRes == 0))
                    {
                        int hr = ObjectFromLresult(lRes, ref IID_IHTMLDocument, 0, ref document);
                        if ((document != null) && (document.body != null))
                        {
                            converstation.Text = document.body.innerHTML;
                        }
                    }
                }
            }
            return result;
        }

        private int EnumWindows(IntPtr hWnd, ref IntPtr lParam)
        {
            int retVal = 1;
            StringBuilder classname = new StringBuilder(128);
            GetClassName(hWnd, classname, classname.Capacity);
            if ((bool)(string.Compare(classname.ToString(), "Internet Explorer_Server") == 0))
            {
                retVal = 0;
                lParam = hWnd;
            }
            return retVal;
        }

        #endregion
    }
}
